Fix auth profiles misclassifying SPOG hosts as workspace configs#4929
Conversation
SPOG hosts (e.g. db-deco-test.gcp.databricks.com) don't match the accounts.* URL prefix, so ConfigType() classifies them as WorkspaceConfig. This causes `auth profiles` to validate with CurrentUser.Me instead of Workspaces.List, which fails for account-scoped SPOG profiles. Use the resolved DiscoveryURL from .well-known/databricks-config to detect SPOG hosts with account-scoped OIDC, matching the routing logic in auth.AuthArguments.ToOAuthArgument(). Also add a fallback for legacy profiles with Experimental_IsUnifiedHost where .well-known is unreachable.
simonfaltum
left a comment
There was a problem hiding this comment.
Review (multi-agent swarm: Isaac + Cursor, cross-reviewed)
Overall the fix is correct and well-targeted. The routing logic properly mirrors ToOAuthArgument(), and the tests are thorough. A few suggestions below, nothing blocking.
Summary of findings
| # | Severity | Finding | File |
|---|---|---|---|
| 1 | suggestion | Legacy fallback ignores workspace_id |
profiles.go:77-82 |
| 2 | suggestion | Logic duplication with arguments.go |
profiles.go:61-75 |
| 3 | suggestion | Misleading test name | profiles_test.go:224 |
| 4 | nitpick | Tests don't prove which validation branch ran | profiles_test.go:85 |
| 5 | nitpick | Unused wantConfigCloud test field |
profiles_test.go:123 |
| 6 | suggestion | Missing negative test case | profiles_test.go |
| 7 | pass | Go code structure checklist | all files |
See inline comments for details.
| // Legacy backward compat: profiles with Experimental_IsUnifiedHost where | ||
| // .well-known is unreachable (so DiscoveryURL is empty). Matches the | ||
| // fallback in auth.AuthArguments.ToOAuthArgument(). | ||
| if configType == config.InvalidConfig && cfg.Experimental_IsUnifiedHost && cfg.AccountID != "" { | ||
| configType = config.AccountConfig | ||
| } |
There was a problem hiding this comment.
suggestion: This legacy fallback always forces AccountConfig regardless of workspace_id. In arguments.go:80-81, ignoring workspace_id is intentional because unified OAuth routing is always account-scoped. But here you're choosing a validation strategy, not an OAuth flow. MatchWorkspaceProfiles in profiler.go treats any non-none workspace_id as workspace-scoped.
If a legacy experimental_is_unified_host profile has a real workspace_id and .well-known is unreachable, this would validate it with Workspaces.List instead of CurrentUser.Me. That said, the combination of Experimental_IsUnifiedHost + real workspace_id is unlikely in practice since workspace-scoped SPOG came after the legacy flag era.
Consider either:
- Adding the same workspace_id branching here as in the discovery block above, or
- Adding a comment explaining why it's intentionally omitted
| configType := cfg.ConfigType() | ||
| isAccountScopedOIDC := cfg.DiscoveryURL != "" && strings.Contains(cfg.DiscoveryURL, "/oidc/accounts/") | ||
| if configType != config.AccountConfig && cfg.AccountID != "" && isAccountScopedOIDC { | ||
| if cfg.WorkspaceID != "" && cfg.WorkspaceID != auth.WorkspaceIDNone { | ||
| configType = config.WorkspaceConfig | ||
| } else { | ||
| configType = config.AccountConfig | ||
| } | ||
| } |
There was a problem hiding this comment.
suggestion (follow-up): The SPOG detection heuristic (DiscoveryURL contains /oidc/accounts/ + IsUnifiedHost fallback) is now duplicated between here and libs/auth/arguments.go:69-82. The cross-referencing comments help, but if the routing logic changes in one place the other can drift.
Consider extracting a shared helper in libs/auth/ in a follow-up, e.g.:
func ResolveConfigType(cfg *config.Config) config.ConfigType { ... }Not blocking for this PR since the logic is simple and well-documented.
There was a problem hiding this comment.
This would be good to have. Bonus points that it moves some of the logic form the SDK to the CLI.
| } | ||
|
|
||
| func TestProfileLoadClassicAccountHost(t *testing.T) { | ||
| // Verify that a host with account-scoped OIDC from discovery is validated |
There was a problem hiding this comment.
suggestion: This test name says "classic account host" but the httptest.Server URL (e.g. http://127.0.0.1:PORT) doesn't match the accounts.* prefix, so ConfigType() won't return AccountConfig for the classic-host reason. This actually exercises the SPOG discovery override path (same as TestProfileLoadSPOGConfigType).
Consider renaming to something like TestProfileLoadSPOGAccountWithDiscovery, or updating the comment to acknowledge that classic accounts.* behavior can't easily be tested with httptest and this serves as a supplementary SPOG case.
| // newProfileTestServer creates a mock server for profile validation tests. | ||
| // It serves /.well-known/databricks-config with the given OIDC shape and | ||
| // responds to the workspace/account validation API endpoints. | ||
| func newProfileTestServer(t *testing.T, accountScoped bool, accountID string) *httptest.Server { |
There was a problem hiding this comment.
nitpick: Both the workspace and account validation endpoints (/api/2.0/preview/scim/v2/Me and /api/2.0/accounts/{id}/workspaces) return success, so Valid == true doesn't prove which branch was taken. Consider making the "wrong" endpoint return an error for each test case to prove routing correctness.
suggestion: Also consider adding a negative test case where .well-known returns 404, account_id is set, Experimental_IsUnifiedHost is false, to verify the profile stays as WorkspaceConfig (proving the override doesn't trigger accidentally).
- Add workspace_id branching to legacy IsUnifiedHost fallback - Rename TestProfileLoadClassicAccountHost to TestProfileLoadSPOGAccountWithDiscovery - Extract hasWorkspace variable to deduplicate condition - Add negative test: no discovery + account_id stays WorkspaceConfig - Fix misleading mock server comments
simonfaltum
left a comment
There was a problem hiding this comment.
Looks good! Clean fix that correctly mirrors the routing logic from ToOAuthArgument().
A few minor observations:
- The
/oidc/accounts/substring check now lives in bothprofiles.goandarguments.go. Worth extracting a shared helper (or at least a constant) if a third consumer appears. - A
log.Debugfwhen the config type gets overridden would help with troubleshooting, consistent with the existing profile load timing log. TestProfileLoadSPOGAccountWithDiscoverycovers the same path as the table-driven case "SPOG account profile validated as account". Could remove it or repurpose it to test a classicaccounts.*host.- The
hasWorkspacebranch in the SPOG override is a no-op whenConfigType()already returnsWorkspaceConfig. Could simplify to only fire when!hasWorkspace, or add a brief comment.
## Summary - Extract the SPOG detection heuristic into a shared `IsSPOG(cfg, accountID)` predicate in `libs/auth/config_type.go`, replacing inline checks in both `profiles.go` and `ToOAuthArgument`. - Extract `ResolveConfigType(cfg)` that wraps `ConfigType()` with SPOG-aware overrides, used by `profiles.go`. - `ToOAuthArgument` now calls `IsSPOG` instead of duplicating the `DiscoveryURL` + `IsUnifiedHost` checks inline. Follow-up to #4929 as suggested in review comment #2 (logic duplication). ### Note on IsUnifiedHost fallback scope The `IsSPOG` predicate checks `Experimental_IsUnifiedHost` unconditionally (not gated on `configType == InvalidConfig`). This is broader than the previous inline check in `profiles.go`, which only fired for `InvalidConfig`. This is safe because the SDK currently returns `InvalidConfig` for all unified hosts (the `UnifiedHost` case was removed from `ConfigType()` in v0.126.0). It is also more robust against future SDK changes that might reclassify unified hosts differently. ## Test plan - [x] `TestResolveConfigType` — 9-case table-driven unit test in `libs/auth/config_type_test.go`. - [x] All existing `TestProfileLoad*` and `TestToOAuthArgument*` tests pass. - [x] `go test ./libs/auth/ ./cmd/auth/` passes.
Summary
ConfigType()classifies hosts by URL prefix (accounts.*→ account, everything else → workspace). SPOG hosts don't match theaccounts.*prefix, so they were misclassified asWorkspaceConfigand validated withCurrentUser.Me, which fails on account-scoped SPOG hosts.DiscoveryURLfrom/.well-known/databricks-configto detect SPOG hosts with account-scoped OIDC (contains/oidc/accounts/), matching the routing logic inauth.AuthArguments.ToOAuthArgument()and the approach from Fix auth logout failing to clear token for workspace profiles with account ID #4853.experimental_is_unified_hostwhere.well-knownis unreachable.Why not just check
account_id?Since #4809,
runHostDiscoverypopulatesaccount_idon every workspace profile from the.well-knownendpoint. A regular workspace profile now routinely carriesaccount_id. The only reliable discriminator is theoidc_endpointshape from.well-known, resolved at runtime (as established in #4853).Test plan
TestProfileLoadSPOGConfigType— table-driven with mock HTTP servers covering SPOG account, SPOG workspace, SPOG withworkspace_id=none, and classic workspace with discovery-populatedaccount_id.TestProfileLoadUnifiedHostFallback—experimental_is_unified_hostprofile with unreachable.well-knownfalls back to account validation.TestProfileLoadClassicAccountHost— classic account-scoped OIDC host.cmd/auth/profiles/spog-account— end-to-end: SPOG profile withworkspace_id=noneshowsvalid:true.go test ./cmd/authandgo test ./acceptance -run TestAccept/cmd/auth/profilespass.